/*
 * Change Logs:
 * Date           Author       Notes
 * 2020-10-15     chenbin
 * 2022-03-27     chenbin
 */
#include "fboot.h"

#define DBG_ENABLE
#define DBG_SECTION_NAME "fboot"
#define DBG_LEVEL DBG_INFO
#include <rtdbg.h>


#if (FBOOT_USING_BOOTLOADER == 1)

int fboot_addr(uint32_t addr)
{
	LOG_I("boot at: 0x%08X", addr);
	fboot_jump_app(addr);
	return 0;
}

int fboot_fal(const char *part_name, uint32_t addr_offset)
{
	uint32_t app_addr = 0;
	const struct fal_partition *part = NULL;
	const struct fal_flash_dev *dev = NULL;

	LOG_I("%s: %s", __FUNCTION__, part_name);
	part = fal_partition_find(part_name);
	if (part != NULL)
	{
		app_addr = part->offset;
		dev = fal_flash_device_find(part->flash_name);
		if (dev)
		{
			app_addr = dev->addr + part->offset + addr_offset;
		}
		fboot_addr(app_addr);
	}
	else
	{
		LOG_I("find fal partition: %s fail", part_name);
	}
	return 0;
}


#if (FBOOT_CUSTOM_JUMP_FUNCTION == 0)
typedef void (*pFunction)(void);
#define USER_BOOT_ADDR 0x08000000
#define USER_APP_ADDR 0x08020000
#define NVIC_VTOR_MASK 0x3FFFFF80

RT_WEAK int fboot_jump_app(uint32_t app_addr)
{
	uint32_t app_entry_stack;
	pFunction app_entry_func;

	app_entry_stack = (*(volatile uint32_t *)app_addr);
	app_entry_func = (pFunction)(*(volatile uint32_t *)(app_addr + 4));

	rt_kprintf("call:0x%08X\n", app_addr);
	rt_kprintf("msp :0x%08X\n", app_entry_stack);
	rt_kprintf("pc  :0x%08X\n", app_entry_func);
	rt_kprintf("\n\n\n");
	if ((app_entry_stack & 0x2FFC0000) == 0x20000000)
	{
#ifdef SCB_DisableICache
		SCB_DisableICache();
#endif
#ifdef SCB_DisableDCache
		SCB_DisableDCache();
#endif
		__disable_irq();
    for (IRQn_Type irq = 0; irq <= 90; irq++)
    {
        NVIC_DisableIRQ(irq);
    }

		SysTick->CTRL = 0;
		SysTick->LOAD = 0;
		SysTick->VAL = 0;

		SCB->VTOR = app_addr & NVIC_VTOR_MASK;

		/* Initialize user application's Stack Pointer */
		__set_MSP(app_entry_stack);
		/* Jump to user application */
		app_entry_func();

		SCB->VTOR = USER_BOOT_ADDR & NVIC_VTOR_MASK;
		//__enable_irq();
	}
	rt_kprintf("call fail !!!\n\n\n");
	return 0;
}
#endif /* FBOOT_CUSTOM_JUMP_FUNCTION */

#if (FBOOT_USING_BOOTAMAIN == 1)

#if (FBOOT_CUSTOM_KEY_STATUS == 0)

RT_WEAK int fboot_get_key_status(void)
{
	return 0;
}
#endif /* FBOOT_CUSTOM_KEY_STATUS */

#include "rtdevice.h"
#include <rthw.h>
static struct rt_semaphore wait_sem;
static rt_err_t _wait_rx_ind(rt_device_t dev, rt_size_t size)
{
	return rt_sem_release(&wait_sem);
}

int fboot_wait_key(uint16_t wait_time)
{
	int res = 0;
	int int_lvl;
	rt_uint16_t odev_flag;
	rt_device_t dev;
	rt_err_t (*odev_rx_ind)(rt_device_t dev, rt_size_t size);
	uint8_t buff[8];
	uint16_t wait_count = 0;
	uint16_t wait_delay = 50;
	uint16_t wait_div = 20;

	rt_thread_mdelay(100);
	dev = rt_console_get_device();
	if (dev)
	{
		rt_sem_init(&wait_sem, "wait", 0, RT_IPC_FLAG_FIFO);

		odev_rx_ind = dev->rx_indicate;
		int_lvl = rt_hw_interrupt_disable();
		rt_device_set_rx_indicate(dev, _wait_rx_ind);

		odev_flag = dev->open_flag;
		/* make sure the device don't change the content. */
		dev->open_flag &= ~RT_DEVICE_FLAG_STREAM;
		rt_hw_interrupt_enable(int_lvl);

		if (rt_device_open(dev, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_STREAM) == RT_EOK)
		{
			rt_kprintf("\n");
			while (wait_time > 0)
			{
				if ((wait_count % wait_div) == 0)
				{
					rt_kprintf("FBOOT>%d\n", wait_time--);
				}
				if (rt_sem_take(&wait_sem, wait_delay) == RT_EOK)
				{
					if (rt_device_read(dev, 0, buff, 1) == 1)
					{
						rt_kprintf("console break\n");
						res++;
						break;
					}
				}
				if (fboot_get_key_status()) // wait key down
				{
					rt_thread_mdelay(wait_delay);
					wait_count++;
					if (fboot_get_key_status()) // check key down
					{
						rt_kprintf("key break\n");
						res++;
						while (fboot_get_key_status()) // wait key up
						{
							rt_thread_mdelay(wait_delay);
							wait_count++;
						}
						break;
					}
				}
				wait_count++;
			}
			rt_device_close(dev);
		}
		/* no rx_ind should be called before the callback has been fully detached.
		 */
		int_lvl = rt_hw_interrupt_disable();
		rt_sem_detach(&wait_sem);

		dev->open_flag = odev_flag;
		rt_device_set_rx_indicate(dev, odev_rx_ind);
		rt_hw_interrupt_enable(int_lvl);
	}
	return res;
}

RT_WEAK void fboot_main(void)
{
	int count = 0;
	count = fboot_wait_key(3);
	if (count > 0)
	{
		rt_kprintf("fboot stop\n");
	}
	else
	{
		fupdate_fal2fal("down", NULL, 1); // check update
		
//		fload_fal("app", 0x80000000U, 0x00200000U, 0x00U);
//		fboot_addr(0x80000000U);
		
		fboot_fal("app", 0x00U);
	}
}

#endif /* FBOOT_USING_MAIN */


#if defined(RT_USING_FINSH) && defined(FINSH_USING_MSH)
#include <finsh.h>

static const char *help_info[] =
{
	"fboot fal   <fal name> <fal offset> (hex/dec)\n\r",
	"fboot addr  <ram addr> (hex/dec)\n\r",
};

void fboot_cmd(rt_uint8_t argc, char **argv)
{
	int temp1 = 0;
	if (argc > 2)
	{
		const char *cmd = argv[1];
		if (!rt_strcmp(cmd, "addr"))
		{
			temp1 = fboot_str2int(argv[2]);
			fboot_addr(temp1);
			return;
		}
		else if (!rt_strcmp(cmd, "fal"))
		{
			temp1 = fboot_str2int(argv[3]);
			fboot_fal(argv[2],temp1);
			return;
		}
	}
	{
		rt_kprintf("usage:\n");
		for (int i = 0; i < sizeof(help_info) / sizeof(char *); i++)
		{
			rt_kputs(help_info[i]);
		}
		rt_kprintf("\n");
	}
}
MSH_CMD_EXPORT_ALIAS(fboot_cmd, fboot, fboot command);

#endif

#endif /* FBOOT_USING_BOOTLOADER */


static int asc2nibble(char c)
{
	if ((c >= '0') && (c <= '9'))
		return c - '0';

	if ((c >= 'A') && (c <= 'F'))
		return c - 'A' + 10;

	if ((c >= 'a') && (c <= 'f'))
		return c - 'a' + 10;

	return 16; /* error */
}

static int hexstr2int(const char* str)
{
	uint32_t numcount = 0;
	int8_t num = 0;
	do
	{
		num = asc2nibble(*str);
		if( (0x00 <= num) && (num <= 0x0F) )
		{
			numcount = numcount << 4;
			numcount |= (num & 0x0F);
		}
		str++;
	}while( (0x00 <= num) && (num <= 0x0F) );
	
	return numcount;
}

#define _ISDIGIT(c) ((unsigned)((c) - '0') < 10)
static int decstr2int(const char *str)
{
	register int i = 0;
	while (_ISDIGIT(*str))
	{
		i = i * 10 + (*(str++) - '0');
	}
	return i;
}

int fboot_str2int(char *strbuffer)
{
	if (strbuffer != RT_NULL)
	{
		if( (strbuffer[0] == '0') && ((strbuffer[1] == 'x') || (strbuffer[1] == 'X')) )
		{
			return hexstr2int(&(strbuffer[2]));
		}
		return decstr2int(strbuffer);
	}
	return 0;
}
